<?php

declare (strict_types = 1);

namespace auth;

use auth\contract\Factory as FactoryContract;
use auth\contract\Guard;
use InvalidArgumentException;

class AuthManager implements FactoryContract
{
    use CreatesUserProvider;

    /**
     * @var \think\App
     */
    protected $app;

    /**
     * @var array
     */
    protected $guards = [];

    /**
     * @param  \think\App  $app
     */
    public function __construct($app)
    {
        $this->app = $app;
    }

    /**
     * @param  string|null  $name
     * @return \auth\contract\Guard
     */
    public function guard(string $name = null): Guard
    {
        $name = $name ?: $this->getDefaultDriver();

        return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
    }

    /**
     * @return string
     */
    public function getDefaultDriver(): string
    {
        return config('auth.default_guard');
    }

    /**
     * @param  string  $name
     * @return \auth\contract\Guard
     *
     * @throws \InvalidArgumentException
     */
    protected function resolve(string $name): Guard
    {
        $config = config("auth.guards.{$name}");

        if (is_null($config)) {
            throw new InvalidArgumentException("Auth guard [{$name}] is not defined.");
        }

        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';
        if (method_exists($this, $driverMethod)) {
            return $this->{$driverMethod}($config);
        }

        throw new InvalidArgumentException(
            "Auth driver [{$config['driver']}] for guard [{$name}] is not defined."
        );
    }

    /**
     * @param $config
     * @return \auth\TokenGuard
     */
    public function createTokenDriver($config): TokenGuard
    {
        return new TokenGuard(
            $this->createUserProvider($config['provider'] ?? null),
            $this->app['request'],
            config('auth.input_key', 'token'),
            config('auth.expiration')
        );
    }

    /**
     * @param  string  $method
     * @param  array  $parameters
     * @return mixed
     */
    public function __call(string $method, array $parameters)
    {
        return $this->guard()->{$method}(...$parameters);
    }
}
